package cn.xinfei.xdecision.data.core.frame.strategy.credit;

import cn.xinfei.xdecision.common.model.datax.consts.VarPrometheusTagsConst;
import cn.xinfei.xdecision.common.model.datax.consts.VariableConst;

import cn.xinfei.xdecision.common.model.datax.enums.CreditChannelEnum;
import cn.xinfei.xdecision.common.model.datax.enums.CreditUploadStatus;
import cn.xinfei.xdecision.common.model.datax.enums.EnumUtil;
import cn.xinfei.xdecision.common.model.datax.enums.VariableStrategyType;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import cn.xinfei.xdecision.common.service.datax.midoffice.MidOfficeSystem;
import cn.xinfei.xdecision.common.service.datax.midoffice.dto.ActiveCreditStartReqDTO;


import cn.xinfei.xdecision.data.model.IVariableRequest;
import cn.xinfei.xdecision.data.model.IVariableResponse;
import cn.xinfei.xdecision.data.core.frame.executors.ICreditNotify;

import cn.xinfei.xdecision.data.core.frame.retry.HttpClientManage;
import cn.xinfei.xdecision.data.core.frame.strategy.BaseStrategy;
import cn.xinfei.xdecision.data.core.frame.strategy.StrategyHandler;
import cn.xinfei.xdecision.data.core.mq.VariableProducer;

import cn.xinfei.xdecision.data.core.util.VariableConvertUtil;
import cn.xinfei.xdecision.data.core.vo.CreditNotifyResp;
import cn.xinfei.xdecision.data.core.vo.credit.CreditChannelRouteRequest;
import cn.xinfei.xdecision.data.core.vo.credit.CreditStatusRequest;
import cn.xinfei.xdecision.model.CreditVarRequest;
import cn.xinfei.xdecision.model.CreditVarResponse;
import com.alibaba.fastjson.JSONObject;

import cn.xinfei.xdecision.data.core.vo.credit.CreditStatusDTO;

import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;

import javax.annotation.PostConstruct;
import java.net.SocketTimeoutException;
import java.util.*;


@StrategyHandler(type = VariableStrategyType.CREDIT)
public class CreditStrategy extends BaseStrategy implements ICreditNotify, ICreditService {

    private final Logger LOGGER = LoggerFactory.getLogger(CreditStrategy.class);

    @Autowired
    MidOfficeSystem midOfficeSystem;

    @Autowired
    VariableProducer variableProducer;

    @Autowired
    ApplicationContext applicationContext;

    @Value("${xdecision.data.credit.route_channel.url}")
    private String creditRuteChannelUrl;
    @Value("${xdecision.data.credit.route_channel.connect_timeout}")
    private int creditRuteChannelConnectTimeout;
    @Value("${xdecision.data.credit.route_channel.read_timeout}")
    private int creditRuteChannelReadTimeout;

    @Value("${xdecision.data.credit.status.url}")
    private String creditStatusUrl;
    @Value("${xdecision.data.credit.status.connect_timeout}")
    private int creditStatusConnectTimeout;
    @Value("${xdecision.data.credit.status.read_timeout}")
    private int creditStatusReadTimeout;
    @Value("${xdecision.data.credit.route_channel.default_channel}")
    private String creditDefaultChannel;

    @Value("${xdecision.data.okhttp.maxIdle}")
    private int maxIdle;

    @Value("${xdecision.data.okhttp.keepAliveDuration}")
    private long keepAliveDuration;


    private HttpClientManage okHttpClientManage = null;

    @Override
    public IVariableResponse process(IVariableRequest request) {
        long begin = System.currentTimeMillis();
        Transaction catT = Cat.newTransaction("credit-datasource", "credit-upload");

        checkStrategyInput(request);
        buildVarContext(request.inputParams());
        checkCreditInputParams(request);

        CreditVarResponse creditVarResponse = new CreditVarResponse();
        if (!request.requestStrategys().contains(VariableStrategyType.CREDIT)) {
            LOGGER.info("request: {} filter credit request", JSONObject.toJSONString(request));
        } else {
            CreditVarRequest creditVarRequest = (CreditVarRequest) request;
            creditVarResponse.setRequestId(creditVarRequest.getRequestId());
            CreditStatusRequest creditStatusRequest = VariableConvertUtil.fromCreditVarRequest(creditVarRequest);
            JSONObject inputParams = request.inputParams();
            Long applyId = Optional.of(inputParams).get().getLong(VariableConst.APPLY_ID);
            String innerApp = Optional.of(inputParams).get().getString(VariableConst.INNER_APP);
            String app = Optional.of(inputParams).get().getString(VariableConst.APP);
            CreditNotifyResp creditNotifyResp;
            JSONObject creditResp = new JSONObject();
            String creditChannel = creditDefaultChannel;
            CreditStatusDTO creditStatusDTO = VariableConvertUtil.toCreditStatusDTO(
                    creditStatusRequest,
                    creditVarRequest.getRequestId(),
                    creditVarRequest.getVarCodes(),
                    applyId,
                    app,
                    innerApp);
            try {
                try {
                    CreditChannelRouteRequest creditChannelRouteRequest = VariableConvertUtil.toCreditRuteChannelDTO(creditStatusDTO);
                    JSONObject creditRouteChannelResponse = creditRouteChannel(creditChannelRouteRequest);
                    creditChannel = creditRouteChannelResponse.getString("result");
                    if (Objects.isNull(EnumUtil.fromCode(CreditChannelEnum.class, creditChannel))) {
                        LOGGER.info("requestId = {} 征信路由接口异常,选择默认渠道={}", creditVarRequest.getRequestId(), creditDefaultChannel);
                        creditChannel = creditDefaultChannel;
                    }
                    creditResp.fluentPut("credit_channel", creditChannel);

                } catch (Exception e) {
                    Cat.logError(e);
                    catT.setStatus(e);
                    LOGGER.error("requestId = {} 征信路由接口异常 for = {} ", creditVarRequest.getRequestId(), e.getCause(), e);
                    throw new XDecisionDataException(XDecisionDataExceptionType.DATA_CREDIT_RUTE_CHANNEL_ERROR, e);
                }
                creditNotifyResp = doCreditNotify(applyId, inputParams, creditChannel);
                onSuccess(() -> {
                    LOGGER.info("requestId = {} request = {} creditNotifyResp:{} success!",
                            creditVarRequest.getRequestId(),
                            JSONObject.toJSONString(request),
                            JSONObject.toJSONString(creditNotifyResp));
                });
                creditResp.fluentPut("status", creditNotifyResp.getStatus());
                creditVarResponse.setData(creditResp.toJSONString());
                creditStatusDTO.setCreditChannel(creditChannel);
                creditStatusDTO.setInputParams(inputParams);
//                applicationContext.publishEvent(new CreditQueryEvent(
//                        this, creditStatusDTO));

                metricCreditUploadStatus(creditNotifyResp.getStatus(),
                        creditChannel,
                        app,
                        innerApp);
                catT.setStatus(Transaction.SUCCESS);
                return creditVarResponse;
            } catch (Exception e) {
                Cat.logError(e);
                catT.setStatus(e);
                onFailure(e);
            } finally {
                long end = System.currentTimeMillis();
                catT.complete();
                Metrics.counter(VarPrometheusTagsConst.CREDIT_UPLOAD_REQUEST_COUNT,
                        VarPrometheusTagsConst.MetricsFliedTags.CREDIT_CHANNEL, creditChannel,
                        VarPrometheusTagsConst.MetricsFliedTags.APP, app,
                        VarPrometheusTagsConst.MetricsFliedTags.INNER_APP, innerApp).increment();

                Set<Tag> tagList = new HashSet<>();
                tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.CREDIT_CHANNEL, creditChannel));
                tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.APP, app));
                tagList.add(Tag.of(VarPrometheusTagsConst.MetricsFliedTags.INNER_APP, innerApp));
                Metrics.gauge(VarPrometheusTagsConst.CREDIT_UPLOAD_DURATION, tagList, end - begin);
            }

        }
        return creditVarResponse;

    }

    @Override
    public CreditNotifyResp doCreditNotify(Long applyId, JSONObject contextData, String channel) {
        //通知业务拉起征信查询
        ActiveCreditStartReqDTO reqDTO = ActiveCreditStartReqDTO.builder()
                .idCardNumber(contextData.getString(VariableConst.ID_CARD_NUMBER))
                .app(contextData.getString(VariableConst.APP))
                .mobile(contextData.getString(VariableConst.MOBILE))
                .creditChannel(channel)
                .type(contextData.getString(VariableConst.TYPE))
                .innerApp(contextData.getString(VariableConst.INNER_APP))
                .orderNumber(contextData.getString(VariableConst.ORDER_NUMBER))
                .build();


        JSONObject jsonObject = midOfficeSystem.activeCreditStartNotify(reqDTO);
        LOGGER.info("doCreditNotify applyId:{},result:{} reqDTO:{}", applyId, jsonObject, JSONObject.toJSONString(reqDTO));
        if (jsonObject != null && jsonObject.getJSONObject("response") != null) {
            JSONObject response = jsonObject.getJSONObject("response");
            if (response.getBoolean("status") != null) {
                if (response.getBoolean("status")) {
                    return new CreditNotifyResp(1);
                }
                // 510002不需要重试的拒绝码
                if (!response.getBoolean("status") && "510002".equals(response.getString("code"))) {
                    //TODO:做拒绝
                    return new CreditNotifyResp(-1);
                }
            }
        }
        return new CreditNotifyResp(0);
    }


    @Override
    public void onSuccess(Runnable r) {

    }

    @Override
    public void onFailure(Throwable throwable) {
        if (throwable instanceof XDecisionDataException) {
            LOGGER.error("error in CreditNotify cause: {}", throwable.getCause(), throwable);
            throw new XDecisionDataException(
                    XDecisionDataExceptionType.DATA_CREDIT_CALL_ERROR,
                    XDecisionDataExceptionType.DATA_CREDIT_CALL_ERROR.text,
                    throwable
            );
        }
    }


    @Override
    public JSONObject status(CreditStatusRequest request, JSONObject inputParams) {
        JSONObject statusRequest = buildCreditStatusRequest(request);
        return doCreditStatus(statusRequest, inputParams);
    }

    @Override
    public JSONObject creditRouteChannel(CreditChannelRouteRequest request) {
        JSONObject ruteChannelRequest = buildCreditRuteChannelRequest(request);
        return doCreditChannelRoute(ruteChannelRequest);
    }


    private JSONObject buildCreditRuteChannelRequest(CreditChannelRouteRequest request) {
        return new JSONObject().fluentPut("id_card_number", request.getIdCardNumber())
                .fluentPut("stage", request.getStage())
//                .fluentPut("stage", "01")
                .fluentPut("inner_app", request.getInnerApp())
                .fluentPut("loan_num", request.getLoanNum());
    }

    private JSONObject buildCreditStatusRequest(CreditStatusRequest request) {
        return new JSONObject().fluentPut("id_card_number", request.getIdCardNumber())
                .fluentPut("id_card_protyle", request.getIdCardProtyle())
                .fluentPut("name", request.getName())
                .fluentPut("mobile", request.getMobile())
                .fluentPut("stage", request.getStage())
//                .fluentPut("stage", "01")
                .fluentPut("credit_channel", request.getCreditChannel());
    }


    private JSONObject doCreditChannelRoute(JSONObject routeChannelRequest) {
        okHttpClientManage = HttpClientManage.getOkHttpClientManage(creditRuteChannelUrl, creditRuteChannelConnectTimeout, creditRuteChannelReadTimeout, maxIdle, keepAliveDuration);
        JSONObject response;
        // 发起请求
        long begin = System.currentTimeMillis();
        try {
            String responseData = okHttpClientManage.post(routeChannelRequest.toJSONString(), HttpClientManage.MEDIATYPE_JSON);
            if (StringUtils.isBlank(responseData)) {
                throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_ERROR, "请求 provider结果为空! inputParam: " + routeChannelRequest.toJSONString());
            }
            long end = System.currentTimeMillis();
            long elementDuration = end - begin;
            LOGGER.info("征信路由时间 = " + elementDuration + " MillSeconds");
            response = JSONObject.parseObject(responseData);
            LOGGER.info("请求返回：creditRuteChannelResult = " + response.toJSONString());
            return response;
        } catch (SocketTimeoutException e) {
            LOGGER.error("获取 征信路由连接超时异常, routeChannelRequest = " + routeChannelRequest.toJSONString(), e);
            throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_TIMEOUT_ERROR, " inputParam: " + routeChannelRequest.toJSONString(), e);

        } catch (Exception e) {
            LOGGER.error("获取 征信路由异常, routeChannelRequest = " + routeChannelRequest.toJSONString(), e);
            throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_ERROR, " inputParam: " + routeChannelRequest.toJSONString(), e);
        }
    }


    private JSONObject doCreditStatus(JSONObject statusRequest, JSONObject inputParams) {
        okHttpClientManage = HttpClientManage.getOkHttpClientManage(creditStatusUrl, creditStatusConnectTimeout, creditStatusReadTimeout, maxIdle, keepAliveDuration);
        JSONObject response;
        // 发起请求
        long begin = System.currentTimeMillis();
        try {
            Map<String, String> rpcContextMap = initRpcContextFromInputParams(inputParams);
            String responseData = okHttpClientManage.postWithNotVerify(statusRequest.toJSONString(), HttpClientManage.MEDIATYPE_JSON, rpcContextMap);
            if (StringUtils.isBlank(responseData)) {
                throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_ERROR, "请求 credit-status 结果为空! inputParam: " + statusRequest.toJSONString());
            }
            long end = System.currentTimeMillis();
            long elementDuration = end - begin;
            LOGGER.info("征信状态查询时间 = " + elementDuration + " MillSeconds");
            response = JSONObject.parseObject(responseData);
            LOGGER.info("请求返回：creditStatusResult = " + response.toJSONString());
            return response;
        } catch (SocketTimeoutException e) {
            LOGGER.error("获取 credit-status 数据源连接超时异常, statusRequest = " + statusRequest.toJSONString(), e);
            throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_TIMEOUT_ERROR, " inputParam: " + statusRequest.toJSONString() + "cause: " + e.getMessage(), e);

        } catch (Exception e) {
            LOGGER.error("获取 credit-status 数据源异常, statusRequest = " + statusRequest.toJSONString(), e);
            throw new XDecisionDataException(XDecisionDataExceptionType.PROVIDER_RESPONSE_ERROR, " inputParam: " + statusRequest.toJSONString() + "cause: " + e.getMessage(), e);
        }
    }

    private void metricCreditUploadStatus(Integer creditStatus, String creditChannel, String app, String innerApp) {
        CreditUploadStatus creditUploadStatus = EnumUtil.fromCodeOrThrow(CreditUploadStatus.class, String.valueOf(creditStatus));
        switch (creditUploadStatus) {
            case SUCCESS:
                Metrics.counter(VarPrometheusTagsConst.CREDIT_UPLOAD_SUCCESS_COUNT,
                        VarPrometheusTagsConst.MetricsFliedTags.CREDIT_CHANNEL, creditChannel,
                        VarPrometheusTagsConst.MetricsFliedTags.APP, app,
                        VarPrometheusTagsConst.MetricsFliedTags.INNER_APP, innerApp).increment();
                break;
            case FAIL:
                Metrics.counter(VarPrometheusTagsConst.CREDIT_UPLOAD_FAIL_COUNT,
                        VarPrometheusTagsConst.MetricsFliedTags.CREDIT_CHANNEL, creditChannel,
                        VarPrometheusTagsConst.MetricsFliedTags.APP, app,
                        VarPrometheusTagsConst.MetricsFliedTags.INNER_APP, innerApp).increment();
                break;
            case DO_NOT_RETRY:
                Metrics.counter(VarPrometheusTagsConst.CREDIT_UPLOAD_MISS_RETRY_COUNT,
                        VarPrometheusTagsConst.MetricsFliedTags.CREDIT_CHANNEL, creditChannel,
                        VarPrometheusTagsConst.MetricsFliedTags.APP, app,
                        VarPrometheusTagsConst.MetricsFliedTags.INNER_APP, innerApp).increment();
                break;
            default:
                throw new XDecisionDataException(XDecisionDataExceptionType.DATA_NO_SUCH_CREDIT_UPLOAD_STATUS_TYPE_ERROR);
        }
    }


}
